package ch.cryptobox.files;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;

import ch.cryptobox.MessageBus;

public class FileWatcher implements Runnable {
	private final Path path;
	private final ArrayList< FileWatcherListener > listeners = new ArrayList<FileWatcherListener>();
	
	public FileWatcher(final String path) {
		this.path = FileSystems.getDefault().getPath(path).toAbsolutePath().normalize();
	}


	
	
	@SuppressWarnings("unchecked")
	private static <T> WatchEvent<T> cast(final WatchEvent<?> event) {
		return (WatchEvent<T>)event;
	}
	
	
	public void addListener(final FileWatcherListener l) {
		listeners.add(l);	
	}
	
	private void fireFileModified(final File file) {		
		for(final FileWatcherListener l : listeners) {
			l.fileModified(file);
		}
	}
	
	
	private void fireFileCreated(final File file) {
		for(final FileWatcherListener l : listeners) {
			l.fileCreated(file);
		}
	}
	
	
	private void fireFileDeleted(final File file) {
		for(final FileWatcherListener l : listeners) {
			l.fileDeleted(file);
		}
	}

	private WatchKey register(final WatchService watcher, final Path path) throws IOException {
		return path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, 
									  StandardWatchEventKinds.ENTRY_DELETE, 
									  StandardWatchEventKinds.ENTRY_MODIFY);
	}
	
	
	
	@Override
	public void run() {
		
		try {
			final WatchService watcher = FileSystems.getDefault().newWatchService();
			WatchKey key = register(watcher, path);
			final ArrayList<Path> subFolders = FileHelper.getSubFolderPaths(path);
			
			for(final Path s: subFolders) {
				//System.out.println(s);
				register(watcher, s);
			}
			
		while(true) {

			try {
				key = watcher.take();	
			} catch (final InterruptedException e) {
				e.printStackTrace();
				return;
			}
			
			for(final WatchEvent<?> event : key.pollEvents()) {
				final Kind<?> kind = event.kind();
				
				if (kind == StandardWatchEventKinds.OVERFLOW) {
					System.err.println("Overflow");
					continue;
				}

				final WatchEvent<Path> ev = cast(event);
				final Path parentPath = (Path) key.watchable();
				final Path filePath = parentPath.resolve(ev.context());
				final File file = filePath.toAbsolutePath().normalize().toFile();
				
				
				//boolean toBeIgnored = MessageBus.getInstance().hasToToBeIgnored(  file.getPath().replace( new File("").getAbsolutePath() + File.separator, "") );
				boolean toBeIgnored = MessageBus.getInstance().hasToToBeIgnored(  file.getPath() );
			
				if(!file.isHidden()) {

					if(kind == StandardWatchEventKinds.ENTRY_CREATE) {	

						
						if(file.isDirectory()) {
							final WatchKey fkey = register(watcher, filePath);
							if(!fkey.isValid()) {
								System.out.println("NotValid");
							}
							
							ArrayList<Path> subfolders = FileHelper.getSubFolderPaths(filePath);
							
							for(final Path p : subfolders) {
								final WatchKey sfKey = register(watcher, p);
								if(!sfKey.isValid()) {
									System.out.println("NotValid");
								} else if(!toBeIgnored){
									
									
									
									fireFileCreated(p.toFile());
								}
							}
							
						}
						
						if(!toBeIgnored)
							fireFileCreated(file);
					} else if(kind == StandardWatchEventKinds.ENTRY_MODIFY ) {
						if(!toBeIgnored)
							fireFileModified(file);
					} else if(kind == StandardWatchEventKinds.ENTRY_DELETE) {
						if(filePath.toFile().isDirectory()) {
							
						}
						
						fireFileDeleted(file);
					}
					
					
					printOpOnFile(kind, file, toBeIgnored);
				}
				
				
				//Reset the key -- this step is critical if you want to receive
				//further watch events. If the key is no longer valid, the directory
				//is inaccessible so exit the loop.
				final boolean valid = key.reset();
				if (!valid) {
					break;
				}
			}
		}
		
		} catch (final IOException e) {
			e.printStackTrace();
		}

	}
	
	//For Debugging
	private void printOpOnFile(final Kind<?>  kind, final File file, boolean ignored) {
		if(ignored) //comment for debugging
			return;
		
			System.out.println("\n[" + kind.name() + "] " + ((file.isDirectory())? "directory" : "file") + ": " + file.getAbsolutePath() + ". " + ((ignored | kind ==  StandardWatchEventKinds.ENTRY_DELETE)? "": " Sending to server..."));
	
//		if(file.isDirectory() || !file.exists())
//			return;
//		
//		if(kind == StandardWatchEventKinds.ENTRY_CREATE || kind == StandardWatchEventKinds.ENTRY_MODIFY ) {
//			BufferedReader reader;
//			try {
//				reader = new BufferedReader( new InputStreamReader( new FileInputStream(file)));
//				String line = reader.readLine();
//
//				while(null != line) {
//					System.out.println(line);
//					line = reader.readLine();
//				}
//				reader.close();
//			
//			} catch (final FileNotFoundException e) {
//				e.printStackTrace();
//			} catch (final IOException e) {
//				e.printStackTrace();
//			}
		//}
	}
}
